﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SpringGraph.Controls;
using SpringGraph.Extensions;
using SpringGraph.ForceLayout;
using System.Collections.Generic;
using System.Windows.Threading;
using SpringGraph.Utils;

namespace SpringGraph
{
    public class SpringGraph : Canvas
    {
        #region Events
        public event EventHandler<EventArgs> Moved;

        internal void DispatchMove()
        {
            if (Moved != null)
                Moved(this, new EventArgs());
        }
        #endregion

        #region Properties
        /** @private */
		protected GraphDataProvider _dataProvider = null;
	    /** @private */
		public Item distinguishedItem;

        public event EventHandler<EventArgs> RepulsionFactorChanged;
        /** @private */
		private double _repulsionFactor = 0.75;
        public double RepulsionFactor
        {
            get
            {
                return _repulsionFactor;
            }
            set
            {
                if (value != RepulsionFactor)
                {
                    _repulsionFactor = value;

                    Move(2);

                    if (RepulsionFactorChanged != null)
                        RepulsionFactorChanged(this, new EventArgs());
                }
            }
        }
	    /** @private */
		private double defaultRepulsion = 100;
        public double DefaultRepulsion
        {
            get
            {
                return defaultRepulsion;
            }
        }
	    /** @private */
		protected ForceDirectedLayout forceDirectedLayout = null;
		/** @private */
		protected Graph _graph;

        private DispatcherTimer timer;      
		//private var itemRendererFactory: IFactory = null;
        private NodeControl dragComponent;
        private double dragStartX;
        private double dragStartY;
        private double dragCursorStartX;
        private double dragCursorStartY;
        private DateTime lastMouseDownTime;
        //private bool paused = false;
        private bool backgroundDragInProgress = false;
        //private var _edgeRenderer: IEdgeRenderer = null;
		//private double prevCoverage = 0;
		//private double prevRepulsionFactor = 0;

        private bool loaded = false;
        private Graph pendingDataProvider = null;

        public Graph DataProvider
        {
            get
            {
                return _graph;
            }
            set
            {
                if (loaded)
                {
                    doSetDataProvider(value);
                }
                else
                {
                    pendingDataProvider = value;
                }
            }
        }

        private GraphDataProvider GraphDataProvider
        {
            get
            {
                return _dataProvider;
            }
        }

        private void doSetDataProvider(Graph value)
        {
            if (_graph != null)
                _graph.Changed -= _graph_Changed;

            _graph = value;

            Rebuild();
            _graph.Changed += new EventHandler<EventArgs>(_graph_Changed);
            Move(40);
        }

        void _graph_Changed(object sender, EventArgs e)
        {
            Rebuild();
        }

        /** [for experimental use]. The layout computations are stopped when the amount of motion
 		 * falls below this threshold. I don't know what the units are,
 		 * the range of meaningful values is from 0.001 to 2.0 or so. Low 
 		 * numbers mean that the layout takes longer to settle down, but gives
 		 * a better result. High numbers means that the layout will stop
 		 * sooner, but perhaps with not as nice a layout. 
 		 */
 		public double MotionThreshold 
        {
            set
            {
                if (value > 0.001 && value < 2)
                    ForceDirectedLayout.MotionLimit = value;
            }
            get
            {
                return ForceDirectedLayout.MotionLimit;
            }
 		}

        private bool _ClipToBounds = true;
        public bool ClipToBounds
        {
            get
            {
                return _ClipToBounds;
            }
            set
            {
                _ClipToBounds = value;
                this.SetValue(Utils.Clip.ToBoundsProperty, value);
            }
        }

        #region Scale
        public event EventHandler<EventArgs> ScaleChanged;
        
        public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register(
            "Scale", typeof(double), typeof(SpringGraph), new PropertyMetadata(1.0, ScalePropertyChanged));

        public double Scale
        {
            get
            {
                return (double)this.GetValue(ScaleProperty);
            }
            set
            {
                if (value != Scale && value > 0.1 && value < 2)
                {
                    this.SetValue(ScaleProperty, value);

                    if (ScaleChanged != null)
                        ScaleChanged(this, new EventArgs());
                }
            }
        }
        
        private static void ScalePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var springGraph = sender as SpringGraph;

            springGraph.ClearEdges();

            foreach (var child in springGraph.Children.Where(c => !(c is NavControl) && !(c is LoadingControl)))
            {
                ScaleTransform t = new ScaleTransform();
                t.ScaleX = t.ScaleY = (double)args.NewValue;
                child.RenderTransform = t;
            }

            springGraph.Move(2);
        }
        #endregion

        #region Flags
        public bool EnableMouseWheelZoom
        {
            get
            {
                return (bool)GetValue(EnableMouseWheelZoomProperty);
            }
            set
            {
                SetValue(EnableMouseWheelZoomProperty, value);
            }
        }

        public static readonly DependencyProperty EnableMouseWheelZoomProperty = DependencyProperty.Register(
            "EnableMouseWheelZoom", typeof(bool), typeof(SpringGraph), new PropertyMetadata(true));

        public bool ShowEdges
        {
            get
            {
                return (bool)GetValue(ShowEdgesProperty);
            }
            set
            {
                SetValue(ShowEdgesProperty, value);
            }
        }

        public static readonly DependencyProperty ShowEdgesProperty = DependencyProperty.Register(
            "ShowEdges", typeof(bool), typeof(SpringGraph), new PropertyMetadata(true));


        public bool LoadingUntilStabilized
        {
            get
            {
                return (bool)GetValue(LoadingUntilStabilizedProperty);
            }
            set
            {
                SetValue(LoadingUntilStabilizedProperty, value);
            }
        }

        public static readonly DependencyProperty LoadingUntilStabilizedProperty = DependencyProperty.Register(
            "LoadingUntilStabilized", typeof(bool), typeof(SpringGraph), new PropertyMetadata(true));

        public bool AjustScaleOnLoading
        {
            get
            {
                return (bool)GetValue(AjustScaleOnLoadingProperty);
            }
            set
            {
                SetValue(AjustScaleOnLoadingProperty, value);
            }
        }

        public static readonly DependencyProperty AjustScaleOnLoadingProperty = DependencyProperty.Register(
            "AjustScaleOnLoading", typeof(bool), typeof(SpringGraph), new PropertyMetadata(true));

        public bool IsBusy
        {
            get
            {
                return (bool)GetValue(IsBusyProperty);
            }
            set
            {
                SetValue(IsBusyProperty, value);
            }
        }

        public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register(
            "IsBusy", typeof(bool), typeof(SpringGraph), new PropertyMetadata(false, IsBusyPropertyChanged));

        private static void IsBusyPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var springGraph = sender as SpringGraph;

            var busy = (bool)args.NewValue;

            if (busy)
            {
                springGraph.ShowLoading();
            }
            else
            {
                springGraph.HideLoading();
            }
        }
        #endregion

        #region LoadingTemplate
        public ControlTemplate LoadingTemplate
        {
            get
            {
                return (ControlTemplate)GetValue(LoadingTemplateProperty);
            }
            set
            {
                SetValue(LoadingTemplateProperty, value);
            }
        }

        public static readonly DependencyProperty LoadingTemplateProperty = DependencyProperty.Register(
            "LoadingTemplate", typeof(ControlTemplate), typeof(SpringGraph), new PropertyMetadata(null));
        #endregion

        #region NodeTemplate
        public ControlTemplate NodeTemplate
        {
            get
            {
                return (ControlTemplate)GetValue(NodeTemplateProperty);
            }
            set
            {
                SetValue(NodeTemplateProperty, value);
            }
        }

        public static readonly DependencyProperty NodeTemplateProperty = DependencyProperty.Register(
            "NodeTemplate", typeof(ControlTemplate), typeof(SpringGraph), new PropertyMetadata(null));

        public IControlTemplateSelector NodeTemplateSelector
        {
            get
            {
                return (IControlTemplateSelector)GetValue(NodeTemplateSelectorProperty);
            }
            set
            {
                SetValue(NodeTemplateSelectorProperty, value);
            }
        }

        public static readonly DependencyProperty NodeTemplateSelectorProperty = DependencyProperty.Register(
            "NodeTemplateSelector", typeof(IControlTemplateSelector), typeof(SpringGraph), new PropertyMetadata(null));
        #endregion

        #region Lines
        public Line DefaultLine
        {
            get
            {
                return (Line)GetValue(DefaultLineProperty);
            }
            set
            {
                SetValue(DefaultLineProperty, value);
            }
        }

        public static readonly DependencyProperty DefaultLineProperty = DependencyProperty.Register(
            "DefaultLine", typeof(Line), typeof(SpringGraph), new PropertyMetadata(null, DefaultLinePropertyChanged));

        private static void DefaultLinePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)

        {
            var springGraph = sender as SpringGraph;
            
            if (springGraph.loaded)
                springGraph.DrawEdges();
        }

        public Line DistinguishedLine
        {
            get
            {
                return (Line)GetValue(DistinguishedLineProperty);
            }
            set
            {
                SetValue(DistinguishedLineProperty, value);
            }
        }

        public static readonly DependencyProperty DistinguishedLineProperty = DependencyProperty.Register(
            "DistinguishedLine", typeof(Line), typeof(SpringGraph), new PropertyMetadata(null, DefaultLinePropertyChanged));

        #endregion

        #endregion

        public SpringGraph()
            : base()
        {
            this.MouseWheel += new MouseWheelEventHandler(SpringGraph_MouseWheel);

            this.MouseLeftButtonUp += new MouseButtonEventHandler(SpringGraph_Mouse_DragEnd);
            this.MouseLeave += new MouseEventHandler(SpringGraph_Mouse_DragEnd);
            this.MouseMove += new MouseEventHandler(SpringGraph_MouseMove_DragContinue);

            this.MouseLeftButtonDown += new MouseButtonEventHandler(SpringGraph_MouseLeftButtonDown_Background);

            this.Loaded += new RoutedEventHandler(SpringGraph_Loaded);
            this.SizeChanged += new SizeChangedEventHandler(SpringGraph_SizeChanged);

            var dp = new GraphDataProvider(this);
			_dataProvider = dp;
			forceDirectedLayout = new ForceDirectedLayout(dp);
            forceDirectedLayout.Moved += new EventHandler<EventArgs>(forceDirectedLayout_Moved);

            this.Background = new SolidColorBrush(Colors.Transparent);

            ClipToBounds = ClipToBounds;
        }

        void SpringGraph_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (EnableMouseWheelZoom)
            {
                var inc = (e.Delta / 120) * 0.2;
                Scale += inc;
            }
        }

        void forceDirectedLayout_Moved(object sender, EventArgs e)
        {
            DispatchMove();
        }

        void SpringGraph_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (!loaded)
                Initialize();
        }

        void SpringGraph_Loaded(object sender, RoutedEventArgs e)
        {
            if (!loaded && !double.IsNaN(this.Height) && !double.IsNaN(this.Width))
                Initialize();
        }

        private void Initialize()
        {
            loaded = true;
            if (pendingDataProvider != null)
            {
                doSetDataProvider(pendingDataProvider);
                pendingDataProvider = null;
            }
            else
                Rebuild();
        }

        #region Move
        public void Move(int moves)
        {
            if (DataProvider != null)
            {
                maxTimerCount = moves;
                initTimerCount = 0;
                DispatcherTimer initTimer = new DispatcherTimer();
                initTimer.Interval = TimeSpan.FromMilliseconds(20);
                initTimer.Tick += new EventHandler(initTimer_Tick);
                initTimer.Start();
            }
        }

        private int initTimerCount = 0;
        private int maxTimerCount = 40;

        void initTimer_Tick(object sender, EventArgs e)
        {
            initTimerCount++;

            if (initTimerCount > maxTimerCount)
            {
                var initTimer = sender as DispatcherTimer;
                initTimer.Stop();
            }
            else
            {
                var inc = initTimerCount % 2 == 0 ? 0.1 : -0.1;

                string id = DataProvider.DistinguishedItem != null ? DataProvider.DistinguishedItem.Id : string.Empty;

                if (string.IsNullOrEmpty(id) && DataProvider.Nodes.Count > 0)
                {
                    id = DataProvider.Nodes.First().Id;
                }

                if (!string.IsNullOrEmpty(id))
                {
                    var node = _dataProvider.FindNode(id);
                    node.View.SetX(node.View.GetX() + inc);
                    node.View.SetY(node.View.GetY() + inc);
                    Refresh();
                }
            }
        }
        #endregion

        public void ClearEdges()
        {
            foreach (var line in lines.ToArray())
            {
                this.Children.Remove(line);
                lines.Remove(line);
            }
        }

        public void DrawEdges()
        {
            if (ShowEdges)
            {
                ClearEdges();

                var edges = _dataProvider.Edges;
                foreach (var edge in edges)
                {
                    var fromNode = edge.FromNode;
                    var toNode = edge.ToNode;
                    DrawEdge(fromNode.View, toNode.View, edge);
                }
            }
        }

        private List<Line> lines = new List<Line>();

        private void DrawEdge(Control control1, Control control2, GraphEdge edge)
        {
            var line = GetLine(edge);

            var center1 = control1.GetPositionCenter(this);
            var center2 = control2.GetPositionCenter(this);

            line.X1 = center1.X;
            line.Y1 = center1.Y;

            line.X2 = center2.X;
            line.Y2 = center2.Y;

            line.SetZIndex(-1);

            this.Children.Add(line);
            lines.Add(line);
        }

        protected virtual Line GetLine(GraphEdge edge)
        {
            var line = new Line();
            Line templatedLine = null;

            if (edge.FromNode.Item == DataProvider.DistinguishedItem
                || edge.ToNode.Item == DataProvider.DistinguishedItem)
                templatedLine = DistinguishedLine;

            if (templatedLine == null)
                templatedLine = DefaultLine;

            if (templatedLine == null)
            {
                line.Fill = new SolidColorBrush(Colors.Black); ;
                line.Stroke = new SolidColorBrush(Colors.Black); ;
                line.StrokeThickness = 2;
            }
            else
            {
                line.Effect = templatedLine.Effect;
                line.IsHitTestVisible = templatedLine.IsHitTestVisible;
                line.Effect = templatedLine.Effect;
                line.Fill = templatedLine.Fill;
                line.Stroke = templatedLine.Stroke;

                foreach(var sd in templatedLine.StrokeDashArray)
                {
                    line.StrokeDashArray.Add(sd);
                }

                line.StrokeDashOffset = templatedLine.StrokeDashOffset;
                line.StrokeEndLineCap = templatedLine.StrokeEndLineCap;
                line.StrokeLineJoin = templatedLine.StrokeLineJoin;
                line.StrokeMiterLimit = templatedLine.StrokeMiterLimit;
                line.StrokeStartLineCap = templatedLine.StrokeStartLineCap;
                line.StrokeThickness = templatedLine.StrokeThickness;
                line.Style = templatedLine.Style;
            }

            return line;
        }

        public Control NewComponent(Item item)
        {
            var control = new NodeControl();

            control.DataContext = item.DataContext;
            control.Item = item;

            if (NodeTemplateSelector != null)
                control.Template = NodeTemplateSelector.SelectTemplate(item.DataContext);

            if (control.Template == null && NodeTemplate != null)
                control.Template = NodeTemplate;

            //if (control.Template == null)
            //    throw new Exception("A NodeTemplate or NodeTemplateSelector is requiered for the type: " + item.DataContext);


            control.SetX(this.GetRealWidth() / 2);
            control.SetY(this.GetRealHeight() / 2);

            control.MouseLeftButtonDown += new MouseButtonEventHandler(control_MouseLeftButtonDown);

            var scale = new ScaleTransform();
            scale.ScaleX = scale.ScaleY = Scale;
            control.RenderTransform = scale;

            this.Children.Add(control);

            return control;
        }

        public void RemoveComponent(Control control)
        {
            this.Children.Remove(control);
        }

        void control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if((DateTime.Now - lastMouseDownTime) < TimeSpan.FromSeconds(0.5)) 
            {
   				// it's a double-click
                var control = sender as NodeControl;
   				var node = _dataProvider.FindNode(control.Item.Id);
   				if(node != null) 
                {
   					SpringGraph_Mouse_DragEnd(sender, e);
   				}
   				return;
   			}
   			lastMouseDownTime = DateTime.Now;
            SpringGraph_MouseMove_DragBegin(sender, e);
        }

         void SpringGraph_MouseMove_DragBegin(object sender, MouseEventArgs e)
        {
            dragComponent = sender as NodeControl;
            var node = _dataProvider.FindNode(dragComponent.Item.Id);
   			dragStartX = dragComponent.GetX();
   			dragStartY = dragComponent.GetY();
   			dragCursorStartX = e.GetStageX(this);
            dragCursorStartY = e.GetStageY(this);
            forceDirectedLayout.DragNode = node;
        }

        void SpringGraph_MouseMove_DragContinue(object sender, MouseEventArgs e)
        {
            if(backgroundDragInProgress) 
            {
                SpringGraph_MouseLeftButtonDown_BackgroundDragContinue(sender, e);
                return;
            }
            if(dragComponent == null) return;
   			
            double deltaX = e.GetStageX(this) - dragCursorStartX;
            double deltaY = e.GetStageY(this) - dragCursorStartY;
            dragComponent.SetX(dragStartX + deltaX);
            dragComponent.SetY(dragStartY + deltaY);
            
            Refresh();
        }

        void SpringGraph_Mouse_DragEnd(object sender, MouseEventArgs e)
        {
            if(backgroundDragInProgress) {
                SpringGraph_MouseLeftButtonDown_BackgroundDragEnd(sender, e);
                return;
            }
            dragComponent = null;
            forceDirectedLayout.DragNode = null;
        }

        void  SpringGraph_MouseLeftButtonDown_Background(object sender, MouseButtonEventArgs e)
        {
            if((DateTime.Now - lastMouseDownTime) < TimeSpan.FromSeconds(0.5)) {
                // it's a double-click
                //var node: GraphNode = _dataProvider.findNode(UIComponent(event.currentTarget));
                //if(node != null) {
                //	dragEnd(event);
                //	Object(node.view).doubleClick();	   	
                //}

                SpringGraph_MouseLeftButtonDown_BackgroundDragEnd(sender, e);

                return;
            }
            lastMouseDownTime = DateTime.Now;
            SpringGraph_MouseLeftButtonDown_BackgroundDragBegin(sender, e);
            e.Handled = true;
        }

        void SpringGraph_MouseLeftButtonDown_BackgroundDragBegin(object sender, MouseButtonEventArgs e)
        {
            backgroundDragInProgress = true;
            /*
            dragComponent = UIComponent(event.currentTarget);
            dragStartX = dragComponent.x;
            dragStartY = dragComponent.y;
            */
            dragCursorStartX = e.GetStageX(this);
            dragCursorStartY = e.GetStageY(this);
        }

        void SpringGraph_MouseLeftButtonDown_BackgroundDragContinue(object sender, MouseEventArgs e)
        {
            double deltaX = e.GetStageX(this) - dragCursorStartX;
            double deltaY = e.GetStageY(this) - dragCursorStartY;
            dragCursorStartX = e.GetStageX(this);
            dragCursorStartY = e.GetStageY(this);
   			
            // apply the delta to all components
            Scroll(deltaX, deltaY);
            //this.UpdateLayout();
            //dragComponent.x = dragStartX + deltaX;
            //dragComponent.y = dragStartY + deltaY;
            Refresh();
        }

        void SpringGraph_MouseLeftButtonDown_BackgroundDragEnd(object sender, MouseEventArgs e)
        {
            backgroundDragInProgress = false;
        }


        /** @private */
  		public void Scroll(double deltaX, double deltaY) 
        {
   			var nodes = this.ChildrenOfType<NodeControl>();
   			foreach(var itemView in nodes)
            {
				itemView.SetX(itemView.GetX() + deltaX);
                itemView.SetY(itemView.GetY() + deltaY);
   			}
            DrawEdges();
  		}

        /**
	     *  Redraw everything. Call this when you changed something that
	     * could affect the size of any of the active itemRenderers. There is
	     * no need to call this when the graph data is changed, we update
	     * automatically in that case.
	     */
        public void Refresh() 
        {
        	if(_dataProvider != null) {
	        	_dataProvider.LayoutChanged = true;
	        	if((forceDirectedLayout != null) && _dataProvider.HasNodes) {
                    forceDirectedLayout.ResetDamper();
		        	if(timer == null)
			        	Tick();
		        }
	        }
        }

        private void Rebuild()  
        {
			if((_graph != null) && (_dataProvider != null)) {
	   	        _dataProvider.Graph = _graph;

                forceDirectedLayout.IsStabilized = false;

                forceDirectedLayout.Stabilized -= forceDirectedLayout_Stabilized;
                if (LoadingUntilStabilized && DataProvider.Nodes.Count > 1)
                {
                    forceDirectedLayout.Stabilized += new EventHandler<EventArgs>(forceDirectedLayout_Stabilized);
                    IsBusy = true;
                }
                else
                    IsBusy = false;

				Refresh();
			}
		}

        void forceDirectedLayout_Stabilized(object sender, EventArgs e)
        {
            CenterGraph();
        }

        private void CenterGraph()
        {
            var bound = GraphDataProvider.Boundary;
            var center = new Point()
            {
                X = bound.X + bound.Width / 2,
                Y = bound.Y + bound.Height / 2
            };

            var screenCenter = this.GetPositionCenter(this);

            double diffX = screenCenter.X - center.X;
            double diffY = screenCenter.Y - center.Y;

            DispatcherTimerWithArgs scrollTimer = new DispatcherTimerWithArgs();
            scrollTimer.Interval = TimeSpan.FromMilliseconds(100);
            scrollTimer.Tick += new EventHandler<DispatcherTimerArgs>(scrollTimer_Tick);

            scrollTimer.Args = new List<double>()
            {
                diffX, diffY
            };

            scrollTimer.Start();
            
        }

        void scrollTimer_Tick(object sender, DispatcherTimerArgs e)
        {
            var timer = sender as DispatcherTimerWithArgs;
            timer.Stop();

            var diffs = e.Args as List<double>;

            Scroll(diffs[0], diffs[1]);

            CheckZoom();
        }

        private void CheckZoom()
        {
            var bounds = GraphDataProvider.Boundary;

            if (Math.Round(Scale, 1) > 0.1 && (bounds.Right > this.GetRealWidth() || bounds.Bottom > this.GetRealHeight()))
            {
                Scale -= 0.1;

                DispatcherTimer zoomTimer = new DispatcherTimer();
                zoomTimer.Interval = TimeSpan.FromMilliseconds(100);
                zoomTimer.Tick += new EventHandler(zoomTimer_Tick);
                zoomTimer.Start();
            }
            else
                IsBusy = false;
        }

        void zoomTimer_Tick(object sender, EventArgs e)
        {
            var timer = sender as DispatcherTimer;
            timer.Stop();

            CheckZoom();
        }

        private void ShowLoading()
        {
            var loading = GetLoading();
            loading.Visibility = Visibility.Visible;
        }

        private void HideLoading()
        {
            var loading = GetLoading();
            loading.Visibility = Visibility.Collapsed;
        }

        private Control GetLoading()
        {
            if (this.ChildrenOfType<LoadingControl>().Count > 0)
                return this.ChildrenOfType<LoadingControl>().FirstOrDefault();
            else
            {
                var loading = new LoadingControl();

                if (LoadingTemplate != null)
                    loading.Template = LoadingTemplate;

                loading.SetZIndex(1000);
                loading.Height = this.GetRealHeight();
                loading.Width = this.GetRealWidth();

                this.Children.Add(loading);
                return loading;
            }
        }

        /** @private */
        protected void Tick() 
        {
            bool moved = forceDirectedLayout.Tick();

            if (moved)
                DrawEdges();

			StartTimer();
        }

        protected void StartTimer() 
        {
            if (timer == null || (timer != null && !timer.IsEnabled))
            {
                timer = new DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(10);
                timer.Tick += new EventHandler(timer_Tick);
                timer.Start();
            }
        }

        void timer_Tick(object sender, EventArgs e)
        {
            Tick();
        }

    }
}
